package com.wxz.ldap.p2;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ldap.core.AttributesMapper;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.filter.AndFilter;
import org.springframework.ldap.filter.EqualsFilter;
import org.springframework.ldap.filter.Filter;
import org.springframework.ldap.query.ContainerCriteria;
import org.springframework.ldap.query.LdapQuery;
import org.springframework.ldap.query.LdapQueryBuilder;
import org.springframework.ldap.query.SearchScope;
import org.springframework.ldap.support.LdapNameBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.naming.InvalidNameException;
import javax.naming.Name;
import javax.naming.NamingException;
import javax.naming.directory.Attributes;
import javax.naming.directory.SearchControls;
import javax.naming.ldap.LdapName;
import java.util.List;

import static org.springframework.ldap.query.LdapQueryBuilder.query;

/**
 * @Author: geng_pool
 * @Description:
 * @Date: Created in 2017/12/27 10:37
 * @Modified by:
 *
 * console命令参考：
 *  ldapsearch -h 192.168.137.14 -x -D "cn=root,dc=wxz,dc=com" -w 666 -b "dc=wxz,dc=com" "cn=user3"

    ldapadd -h 192.168.137.14 -x  -D  "cn=root, dc=wxz, dc=com"  -w  666  -f insert.ldif

    ldapmodify -h 192.168.137.14 -x  -D  "cn=root, dc=wxz, dc=com"  -w  666  -f  modify.ldif

    ldapdelete -h 192.168.137.14 -x -D "cn=root,dc=wxz,dc=com" -w 666 "cn=user4,ou=managers,dc=wxz,dc=com"
 *
 */
@Service
public class OdmPersonRepo {

    @Autowired
    private LdapTemplate ldapTemplate;

    public void createUserByCn() {
        /**
         * 这里不能指定base，就像ldapadd一样，不能使用base，直接在appliyaml中base的基础上开始写
         */
        LdapName dn = LdapNameBuilder.newInstance()
                .add("ou", "staff")
                .add("cn", "user7")
                .build();
        User user = new User("user7", "sn7", dn);
        ldapTemplate.create(user);
    }

    /**
     *  这种方法会将sn作为用户节点的uid，当使用dn来查询的时候，会有问题。
     *  1.dn查询要求uid必须是cn，而不能使sn，所以会报错。
     *  2.但是用sn作为uid来进行update或者delete是没有问题的。
     *  斟酌使用，最好还是cn作为uid
     */
    public void createUserBySn() {
        LdapName dn = LdapNameBuilder.newInstance()
                .add("ou", "staff")
                .add("sn", "sn8")
                .build();
        User user = new User("cn8", "sn8", dn);
        ldapTemplate.create(user);
    }

    /**
     * 通过cn查找
     * @param cn
     * @return
     */
    public List<User> findByCn(String cn) {
        //where("objectClass").is("Person")这里不需要再写，因为model中已经指定
        return ldapTemplate.find(query().base("dc=wxz,dc=com").where("cn").is(cn), User.class);
    }

    /**
     * 查询列表
     * @return
     */
    public List<User> findList(){
        /**
         * 注意此处的base为指定查询的相对目录，因为application.yaml中已经定义了
         * base: dc=wxz,dc=com
         * 此处只需要再次基础上继续指定即可。所以指定ou=staff
         */

        //1.使用search(),(不太好用，麻烦而且部分属性目前没有找到映射办法，例如ou)================
        /*List searchRst = ldapTemplate.search("ou=staff", "(cn=user3)", new AttributesMapper() {
            @Override
            public Object mapFromAttributes(Attributes attributes) throws NamingException {
                User u = new User();
                u.setCn(attributes.get("cn").get().toString());
                u.setSn(attributes.get("sn").get().toString());
                return u;
            }
        });
        System.out.println(searchRst);*/

        //2222.条件查询ou下的多个，非所有，即必须添加至少一个条件================
        LdapQuery filter = LdapQueryBuilder.query()
                .base("ou=managers")
                .searchScope(SearchScope.SUBTREE).filter("(cn=user1)");//这里的括号不能少
                //.where("cn") //可以用上面的硬编码filter，也可以用这里的链式写法，但是返回值是不一样的，但都是LdapQuery的子类
                //.is("user1");
        List<User> user = ldapTemplate.find(filter, User.class);

        //3333.条件查询ou下的多个，可以是所有,这取决于是否添加and条件。推荐这种办法================
        AndFilter andFilter = new AndFilter();
        andFilter.and(new EqualsFilter("cn","user2"));
        andFilter.and(new EqualsFilter("sn","xianzhang"));

        SearchControls searchCtl = new SearchControls();

        List<User> r = ldapTemplate.find(LdapNameBuilder.newInstance("ou=managers").build(),
                andFilter,
                searchCtl,
                User.class);

        //4444.查询给定base下的所有，这里的newInstance必须指定一个参数作为base，不指定不能用。================
        // 如果想不指定来查询所有，应该使用上一个方法：ldapTemplate.findAll(User.class);
        List<User> allByOu = ldapTemplate.findAll(LdapNameBuilder.newInstance("ou=managers").build(),
                searchCtl,
                User.class);

        //5555.查询所有(User类型中指定了objectClass为Person)================
        List<User> all = ldapTemplate.findAll(User.class);

        return null;
    }

    /**
     * 通过ou查找
     * ldapTemplate.search()系列方法需要手动书写属性映射类，
     * ldapTemplate.find()系列方法不需要手动书写属性映射类，在model上使用annotation即可
     * @param ou
     * @return
     */
    public List<User> findByOu(String ou) throws InvalidNameException {
        if(StringUtils.isEmpty(ou)){
            throw new RuntimeException("请填写ou");
        }
        LdapName name = LdapNameBuilder.newInstance("ou=" + ou).build();
        List<User> userInOu = ldapTemplate.findAll(name, new SearchControls(), User.class);
        return userInOu;
    }

    /**
     * 通过ODM修改
     */
    public void updateByODM() {
        /**
         * 注意：dn中的部分，是不允许修改的，如果cn为uid，则cn不能改，sn可以改；如果sn作为uid，则sn不能改，cn可以改
         *
         * LdapName构造dn进行查询的时候，cn必须是dn中的一部分，必须用cn来查，sn不行。但是进行update或者delete
         * 的时候，可以用sn，不必非用dn。
         */
        LdapName dn = LdapNameBuilder.newInstance()
                .add("ou", "staff")
                .add("sn", "sn8")
                .build();

        User user = new User();
        user.setDn(dn);
        user.setSn("sn8");
        user.setCn("cn88");

        ldapTemplate.update(user);
    }


    public void updateByAttribute(){
        /**
         * 详见：https://www.cnblogs.com/lyh421/p/6688060.html#_label1_2
         */
    }

    /**
     * 删除
     */
    public void deleteUser() {
        //通过dn删除，删除时候，指定的uid可以是cn或者是sn都可以
        Name dn = LdapNameBuilder.newInstance().add("ou", "staff").add("cn", "user6").build();
        ldapTemplate.delete(new User(null,null,dn));
    }

}